
#include "ioctl.h"

#ifdef GMEM_DEBUGB
#define dbg_print(fmt, ...)			\
	do{								\
		printk("GMEM: ");			\
		printk(fmt, ##__VA_ARGS__);	\
		printk("\r\n");				\
	}while(0)
#else
#define dbg_print(fmt, ...)
#endif

static uint8_t *gmem_buffer;
static uint32_t gmem_size = 1024;
static uint32_t data_length = 0;

//打开设备
static int gmem_open(struct inode *inode, struct file *file)
{
	return 0;
}

//释放设备
int gmem_release(struct inode *inode, struct file *file)
{
	return 0;
}

//读数据
ssize_t gmem_read(struct file *file, char __user *buffer, size_t size, loff_t *pos)
{
	uint32_t tmp_size;

	//检查是否溢出
	if(*pos > data_length)
		return -EAGAIN;
	if(*pos == data_length)
		return 0;
	//重新计算读取的大小
	if((*pos + size) > data_length)
		size = data_length - *pos;

	//拷贝数据到应用层
	tmp_size = copy_to_user(buffer, &gmem_buffer[*pos], size);
	//获取成功拷贝的字节数
	tmp_size = size - tmp_size;

	//维护读写偏移
	*pos += tmp_size;

	return tmp_size;
}

//写设备
ssize_t gmem_write(struct file *file, const char __user *buffer, size_t size, loff_t *pos)
{
	uint32_t tmp_size;

	//检查是否溢出
	if(*pos > gmem_size)
		return -EAGAIN;
	if(*pos == gmem_size)
		return 0;
	//重新计算写入的大小
	if((*pos + size) > gmem_size)
		size = gmem_size - *pos;

	//将应用层数据拷贝到全局内存
	tmp_size = copy_from_user(&gmem_buffer[*pos], buffer, size);
	//获取成功拷贝的字节数
	tmp_size = size - tmp_size;

	//维护读写偏移
	*pos += tmp_size;
	//维护数据有效长度
	data_length += tmp_size;

	return tmp_size;
}

//控制设备
long gmem_ioctl32(struct file *file, unsigned int request, unsigned long arg)
{
	uint8_t *new_mem;
	uint8_t *old_mem;
	uint32_t new_size;
	uint32_t copy_length;

	//检查设备类型
	if(_IOC_TYPE(request) != GMEM_MAGIC)
		return -EINVAL;
	//提取命令码
	switch(_IOC_NR(request)) 
	{
		case 0:
			//重新设置全局内存的大小
			dbg_print("set memory size\r\n");
			//检查新的大小是否合法
			if(arg == 0)
				return -EINVAL;
			//获取新的全局内存大小
			new_size = arg;
			//计算数据拷贝长度
			if(new_size >= data_length)
				copy_length = data_length;
			else
				copy_length = new_size;
			//重新分配内存
			new_mem = vmalloc(new_size);
			//将数据拷贝到新内存
			memcpy(new_mem, gmem_buffer, copy_length);
			//修改当前数据长度和读写偏移
			data_length = copy_length;
			if(file->f_pos > data_length)
				file->f_pos = data_length;
			//将gmem_buffer切换到新分配的内存
			old_mem = gmem_buffer;
			gmem_buffer = new_mem;
			gmem_size = new_size;
			//释放以前的内存
			vfree(old_mem);
			break;
		case 1:
			//获取全局内存的大小
			dbg_print("get memory size\r\n");
			//检查内存地址是否正常
			if(arg == 0)
				return -EINVAL;
			//检查读取的字节数是否正常
			if(_IOC_SIZE(request) != sizeof(uint32_t))
				return -EINVAL;
			//读取全局内存的大小字段到应用层
			if(copy_to_user((void*)arg, &gmem_size, _IOC_SIZE(request)))
				return -EIO;
			break;
		case 2:
			//复位全局内存的值
			dbg_print("clean memory\r\n");
			//设置内存为0
			memset(gmem_buffer, 0, gmem_size);
			//复位数据长度和读写偏移
			data_length = 0;
			file->f_pos = 0;
			break;
		default:
			return -EINVAL;
	}
	return 0;
}

//功能与unlocked_ioctl一样，只有在64位系统中运行32位程序时才会调用
long gmem_ioctl64(struct file *file, unsigned int request, unsigned long arg)
{
	return gmem_ioctl32(file, request, arg);
}

//设备指针定位
loff_t gmem_llseek(struct file *file, loff_t offset, int whence)
{
	loff_t temp_pos;

	temp_pos = 0;
	switch(whence)
	{
		case SEEK_SET:
			temp_pos = offset;
			break;
		case SEEK_CUR:
			temp_pos = file->f_pos + offset;
			break;
		case SEEK_END :
			temp_pos = gmem_size + offset;
			break;
		default:
			temp_pos = -EINVAL;
			break;
	}

	if((temp_pos >= gmem_size) || (temp_pos < 0))
		temp_pos = -EINVAL;
	else
		file->f_pos = temp_pos;

	dbg_print("f_pos = %lld\r\n", file->f_pos);

	return temp_pos;
}

struct file_operations fops = {
	.open = gmem_open,
	.release = gmem_release,
	.read = gmem_read,
	.write = gmem_write,
	.unlocked_ioctl = gmem_ioctl32,
	.compat_ioctl = gmem_ioctl64,
	.llseek = gmem_llseek,
};

struct miscdevice gmem_misc = {
	.minor = GMEM_MINOR,
	.name = GMEM_NAME,
	.fops = &fops,
};

static int __init gmem_init(void)
{
	int err = 0;

	printk("global memory init\r\n");

	gmem_buffer = vmalloc(gmem_size);
	if(gmem_buffer == NULL)
	{
		dbg_print("malloc mem failed\r\n");
		return -ENOMEM;
	}

	err = misc_register(&gmem_misc);
	if(err != 0)
	{
		dbg_print("register misc failed\r\n");
		return err;
	}
	return 0;
}

static void __exit gmem_exit(void)
{
	printk("global memory exit\r\n");

	vfree(gmem_buffer);
	misc_deregister(&gmem_misc);
}

module_init(gmem_init);
module_exit(gmem_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("lf");
MODULE_DESCRIPTION("ioctl test");
MODULE_ALIAS("gmem");

